﻿using System;
using Babylon.Toolbox;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Babylon.Primitives
{
    /// <summary>
    /// Base class for all cube primitives
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class CubeBase<T> : IDrawableShape where T:struct, IPositionNormal
    {
        private readonly float _width;
        private readonly float _height;
        private readonly float _depth;

        /// <summary>
        /// get or set the device used to load content or draw the shape
        /// </summary>
        public GraphicsDevice Device { get; set; }

        /// <summary>
        /// get the bounding box of the shape
        /// </summary>
        public BoundingBox BoundingBox { get; private set; }

        /// <summary>
        /// get the bounding sphere of the shape
        /// </summary>
        public BoundingSphere BoundingSphere { get; private set; }

        /// <summary>
        /// get or set the effect associated with the shape
        /// </summary>
        public Effect Effect { get; set; }

        /// <summary>
        /// Draw the shape using the associated effect
        /// </summary>
        public void Draw(){
            if(Effect == null)
                throw new InvalidOperationException("Effect is null");
            if(Device == null)
                throw new InvalidOperationException("Device is null");
            Effect.Apply();
            Device.SetVertexBuffer(_vb);
            Device.Indices = _ib;
            Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _vertices.Length, 0, _indices.Length / 2);
        }


        public void DrawFace(int faceIx)
        {
            Effect.Apply();
            Device.SetVertexBuffer(_vb);
            Device.Indices = _ib;
            Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _vertices.Length, 6 * faceIx, 2);
        }

        private T[] _vertices;
        private VertexBuffer _vb;
        private IndexBuffer _ib;
        private static ushort[] _indices = new ushort[]
            {0,1,2 ,2,1,3,
             4,5,6 ,6,5,7,
             8,9,10, 10,9,11,
             12,13,14, 14,13,15,
             16,17,18, 18,17,19,
             20,21,22, 22,21,23};



        protected CubeBase(float width, float height, float depth){
            _width = width;
            _height = height;
            _depth = depth;

            BoundingBox = new BoundingBox(new Vector3(-0.5f * _width, -0.5f * _height, -0.5f * _depth), new Vector3(0.5f * _width, 0.5f * _height, 0.5f * _depth));
            BoundingSphere = new BoundingSphere(new Vector3(0, 0, 0), new Vector3(-0.5f * _width, -0.5f * _height, -0.5f * _depth).Length());
           
        }



        protected abstract void BeforeCreatingVertexArray();
        protected abstract T CreateVertex(Vector3 position, Vector3 normal);
        protected abstract void AfterCreatingVertexArray(T[] vertices);
        protected abstract VertexDeclaration GetVertexDeclaration();


        /// <summary>
        /// Load content (initialize buffers)
        /// </summary>
        public void LoadContent(){
            BeforeCreatingVertexArray();
            _vertices = new T[]{
                // face
                CreateVertex(new Vector3(-0.5f * _width,  0.5f*_height, 0.5f* _depth), Vector3.Backward), 
                CreateVertex(new Vector3( 0.5f * _width,  0.5f*_height, 0.5f* _depth), Vector3.Backward), 
                CreateVertex(new Vector3(-0.5f * _width, -0.5f*_height, 0.5f* _depth), Vector3.Backward), 
                CreateVertex(new Vector3( 0.5f * _width, -0.5f*_height, 0.5f* _depth), Vector3.Backward), 

                // right
                CreateVertex(new Vector3( 0.5f * _width,  0.5f*_height,  0.5f* _depth), Vector3.Right),
                CreateVertex(new Vector3( 0.5f * _width,  0.5f*_height, -0.5f* _depth), Vector3.Right),
                CreateVertex(new Vector3( 0.5f * _width, -0.5f*_height,  0.5f* _depth), Vector3.Right),
                CreateVertex(new Vector3( 0.5f * _width, -0.5f*_height, -0.5f* _depth), Vector3.Right),

                // left
                CreateVertex(new Vector3(-0.5f * _width,  0.5f*_height, -0.5f* _depth), Vector3.Left),
                CreateVertex(new Vector3(-0.5f * _width,  0.5f*_height,  0.5f* _depth), Vector3.Left),
                CreateVertex(new Vector3(-0.5f * _width, -0.5f*_height, -0.5f* _depth), Vector3.Left),
                CreateVertex(new Vector3(-0.5f * _width, -0.5f*_height,  0.5f* _depth), Vector3.Left),

                // back                
                CreateVertex(new Vector3( 0.5f * _width,  0.5f*_height, -0.5f* _depth), Vector3.Forward),
                CreateVertex(new Vector3(-0.5f * _width,  0.5f*_height, -0.5f* _depth), Vector3.Forward),
                CreateVertex(new Vector3( 0.5f * _width, -0.5f*_height, -0.5f* _depth), Vector3.Forward),
                CreateVertex(new Vector3(-0.5f * _width, -0.5f*_height, -0.5f* _depth), Vector3.Forward),

                // top
                CreateVertex(new Vector3(-0.5f * _width,  0.5f*_height, -0.5f* _depth), Vector3.Up),
                CreateVertex(new Vector3( 0.5f * _width,  0.5f*_height, -0.5f* _depth), Vector3.Up),
                CreateVertex(new Vector3(-0.5f * _width,  0.5f*_height,  0.5f* _depth), Vector3.Up),
                CreateVertex(new Vector3( 0.5f * _width,  0.5f*_height,  0.5f* _depth), Vector3.Up),

                //bottom
                CreateVertex(new Vector3(-0.5f * _width, -0.5f*_height,  0.5f* _depth), Vector3.Down),
                CreateVertex(new Vector3( 0.5f * _width, -0.5f*_height,  0.5f* _depth), Vector3.Down),
                CreateVertex(new Vector3(-0.5f * _width, -0.5f*_height, -0.5f* _depth), Vector3.Down),
                CreateVertex(new Vector3( 0.5f * _width, -0.5f*_height, -0.5f* _depth), Vector3.Down),            
        };
            AfterCreatingVertexArray(_vertices);


            _vb = new VertexBuffer(Device, GetVertexDeclaration(), _vertices.Length, BufferUsage.None);
            _ib = new IndexBuffer(Device, IndexElementSize.SixteenBits, _indices.Length, BufferUsage.None);

            _vb.SetData(0, _vertices, 0, _vertices.Length, GetVertexDeclaration().VertexStride);
            _ib.SetData(0, _indices, 0 , _indices.Length);
        }
    }
}
